#include "ip.h"

pf_notificacion_ip ip_notificaciones[256] = { 0 } ;

BYTE ip_mask[IP_ALEN] = {0};
BYTE ip_router[IP_ALEN] = {0};

int ip_inicializado = 0;

uint16_t ip_identificacion = 0;

#define GLOBAL_IP_MASK "IPMASK"
#define GLOBAL_IP_ROUTER "IPROUTER"

#define IP_OK 0
#define IP_ERROR -1

#ifdef DEBUG
#define ASSERT(str) printf(str)
#endif
#ifndef DEBUG
#define ASSERT(str) ;
#endif

int get_ip(BYTE * dest, char * global_name) {
  char * tmp;
  tmp = getenv(global_name);
  if(tmp != NULL && lee_cadena_ip(tmp, dest) == 0) {
    return IP_OK;
  } else {
    return IP_ERROR;
  }
} 

int notificacion_ip(const BYTE * dir_origen, int longitud, const BYTE* mensaje, int tipo, struct timeval * timestamp)
{
  uint16_t header_len;
  uint16_t total_len;
  uint16_t checksum;

  const BYTE * ip_origen;
  const BYTE * ip_destino;

  const BYTE * p_data;

  op_ip options;

  ASSERT("Mensaje recibido\n");
  
  if(longitud < IP_HLEN_MIN) {
    ASSERT("Descartando debido a longitud\n");
    return ETH_ERROR;
  }

  header_len = (mensaje[0] & 0x0F) << 2; 
//  total_len  = mensaje[2] << 8 | mensaje[3];
  memcpy(&total_len,mensaje+2, sizeof(uint16_t));
  total_len  = ntohs(total_len);
  

  if(header_len > total_len) {
    ASSERT("Descartando debido a longitud\n");
    return ETH_ERROR;
  }

  if(ip_notificaciones[mensaje[9]] == NULL)
    return ETH_ERROR;
  
  calcula_checksum_ip(header_len, (BYTE*)mensaje,(BYTE*) &checksum);
  if(checksum != 0) {								/*Angel: He cambiado esto. Cuando el mensaje es correcto, el checksum es 0*/
    ASSERT("Descartando debido a checksum\n");
    return ETH_ERROR;
  }

  if((mensaje[6] & 0x20) || (mensaje[6] & 0x1F) || mensaje[7]) {
    ASSERT("Descartando debido a fragmentacion\n");
    return ETH_ERROR; /* MENSAJE FRAGMENTADO */
  }

  ip_origen = mensaje + 12;
  ip_destino = mensaje + 16;

  p_data = mensaje + header_len;
  if(memcmp(ip_destino, dir_ip_local, IP_ALEN) != 0) {
    ASSERT("Descartando debido a direccion destino\n");
    return ETH_ERROR;
  }

  options.ttl = mensaje[8];
  if(mensaje[6] & 0x40) /* Bit DF: 010 00000 */
    options.df = 1;
  else
    options.df = 0;
  options.tos = mensaje[1]; /* TOS */
  memcpy(options.extra, mensaje +20, header_len - 20);
  options.elen = header_len - 20;

  ASSERT("Delegando a nivel superior\n");
  if(ip_notificaciones[mensaje[9]]((BYTE*)ip_origen,
				       total_len - header_len,
				       (BYTE*)p_data, &options)!=IP_OK){
  	ASSERT("Mensaje rechazado por el nivel superior\n");
  }
  
  return ETH_ERROR;		/*El mensaje siempre debe rechazarse para liberar el buffer de entrada de ethd*/
}

int ip_inicializa() {
  if(!ip_inicializado) {
    if(arp_inicializa() == IP_ERROR)
      return IP_ERROR;
      
    if(InicializarNivel1a(1, 0, notificacion_ip, 0) == IP_ERROR) {
      arp_finaliza();
      return IP_ERROR;
    }
    
    if(get_ip(ip_mask, GLOBAL_IP_MASK) != IP_OK || get_ip(ip_router, GLOBAL_IP_ROUTER) != IP_OK) {
      FinalizarNivel1a();
      return IP_ERROR;
    }
    ip_inicializado = 1;
    ip_identificacion = 0;
  }
  return IP_OK;
}

int ip_finaliza() {
  short status=IP_OK;
  
  if(ip_inicializado) {
    ip_inicializado = 0;
    if(arp_finaliza()!=0){
      status=IP_ERROR;
    }
    
    if(FinalizarNivel1a()!=0){
      status=IP_ERROR;
    }
    return status;
  }

  return ETH_OK;
}

int ip_registra_protocolo(BYTE protocolo, pf_notificacion_ip callback) {
  if(!ip_inicializado)
    return IP_ERROR;

  ip_notificaciones[protocolo] = callback;
  return IP_OK;
}

int is_in_local_segment(BYTE * ip) {
  int i;
  for(i = 0; i < IP_ALEN; i++) {
    if((ip[i] & ip_mask[i]) != (dir_ip_local[i] & ip_mask[i]))
      return 0;
  }
  return 1;
}

#define PADDING(n) (((n)%4 == 0) ? 0 : (4 - ((n)%4)))
int ip_envia_datagrama(BYTE protocolo, BYTE * dir_ip_destino, unsigned int tamano, 
		       BYTE * datos, op_ip * opciones) {
  BYTE * datagrama;
  BYTE mac_remota[ETH_ALEN];
  uint16_t tamano_total;
  uint16_t tamano_total_net;

  if(!ip_inicializado || tamano > 65515)
    return IP_ERROR;

  if(is_in_local_segment(dir_ip_destino)) {
    puts("Solicitando direccion ip local\n");
    if(arp_solicita_direccion(dir_ip_destino, mac_remota) == -1){
    	puts("Error al solicitar ip local");
      return IP_ERROR;
    }
  } else {
    ASSERT("Solicitando direccion ip del gateway\n");
    if(arp_solicita_direccion(ip_router, mac_remota) == -1)
      return IP_ERROR;
  }

  ASSERT("Direccion obtenida\n");



  if(opciones) {
    if(opciones->elen <= IP_HLEN_MAX - IP_HLEN_MIN)
      tamano_total = IP_HLEN_MIN + tamano + opciones->elen + PADDING(opciones->elen);		/*Angel: Creo que hay que ajustar PADDING para que sean opciones con tamanno multiplo de 32 bits*/
    else
      return IP_ERROR;
  } else {
    tamano_total = IP_HLEN_MIN + tamano;
  }

  datagrama = calloc(sizeof(BYTE), tamano_total);
  if(!datagrama) return IP_ERROR;

  ASSERT("Datagrama reservado\n");

  tamano_total_net = htons(tamano_total);

  datagrama[0] = (0x04 << 4) | (tamano_total - tamano)/4;		/*Angel: He cambiado esto. El tamano de cabecera se mide en bloque de 32 bits, asi que hay que dividir entre 4*/
  datagrama[1] = (opciones?opciones->tos:IP_TOS_DEFECTO);

  memcpy(datagrama+2, &tamano_total_net, sizeof(uint16_t));
//  datagrama[2] = tamano_total_net >> 8;
//  datagrama[3] = tamano_total_net & 0x00FF;

  datagrama[4] = htons(ip_identificacion) >> 8;
  datagrama[5] = htons(ip_identificacion) & 0x00FF;

  datagrama[6] = (opciones?opciones->df:IP_DF_DEFECTO) << 6;
  datagrama[7] = 0;

  datagrama[8] = (opciones?opciones->ttl:IP_TTL_DEFECTO);
  datagrama[9] = protocolo;

  datagrama[10] = 0; /* CHECKSUM */
  datagrama[11] = 0; /* CHECKSUM */

  ASSERT("Cabecera copiada\n");

  memcpy(datagrama + 12, dir_ip_local, IP_ALEN);
  memcpy(datagrama + 16, dir_ip_destino, IP_ALEN);

  ASSERT("Direcciones destino / origen copiadas\n");

  if(opciones && opciones->elen != 0)
    memcpy(datagrama + 20, opciones->extra, opciones->elen);

  memcpy(datagrama + tamano_total - tamano, datos, tamano);

  calcula_checksum_ip(tamano_total - tamano, datagrama, datagrama + 10);	/*Angel: Te he cambiado esto, porque llamabas a calcula_crc xD*/

  if(EnviarDatagramaNivel1a(mac_remota, tamano_total, datagrama, 1) == ETH_OK) {
    free(datagrama);
    return IP_OK;
  } else {
    free(datagrama);
    return IP_ERROR;
  }
}
  


  
    
  

  
